/*
 * Framework.cpp
 *
 *  Created on: 2011/06/05
 *      Author: naoki
 */

#include "NTEngine/NTEngine.h"
#include "NTEngine/Core/Core.h"
#include "NTEngine/Base/Queue.h"
#include "NTEngine/Base/DebugStream.h"
#include "NTEngine/Graphics/Manager.h"
#include "NTEngine/Input/Manager.h"
#include "NTEngine/Sound/Manager.h"
#include "NTEngine/Threading/Function.h"
#include "NTEngine/Threading/Manager.h"
#include "NTEngine/FileIO/Manager.h"
#include "NTEngine/Net/Manager.h"
#include "Framework.h"
#include "DebugScreenImpl.h"

#include <string>
#include <fstream>

namespace NTEngine {
using namespace std;
namespace { // 無名

#define ENABLE_TIME_CALC 0

class Impl {
public:
	Impl()
	: mPreTime( 0 )
	, mElapsedTime( 0 )
#if ENABLE_TIME_CALC
	, mRequestFps( 60 )
	, mBresenhamError( 0 )
#endif
	, mTimeQ( 60 )
	, mFrameRate( 0.F )
	, mExtraThreadNumber( 0 )
	, mLoadMode( FileIO::Manager::MODE_DIRECT_FIRST )
	, mArchiveNames( 0 )
	, mArchiveNumber( 0 )
	, mEnableDepthStencil( true )
	, mEnableAntialias( false )
	, mEnableAnisotropicFilter( false )
	, mTranslucent( false )
	{
		// デバッグストリーム開始
		cout.begin();
		unsigned t = Core().time();
		for ( int i = mTimeQ.capacity() - 1; i >= 0; --i ) {
			mTimeQ.pushBack( t );
		}
		mPreTime = t;
	}

	~Impl() {
		if ( mArchiveNames ){
			SAFE_DELETE_ARRAY( mArchiveNames );
		}
		SAFE_DELETE( gDebugScreenImpl );

		Sound::Manager::destroy();
		Input::Manager::destroy();
		Graphics::Manager::destroy();
		Net::Manager::destroy();
		FileIO::Manager::destroy();
		Threading::Manager::destroy();

		// デバッグストリーム終了
		cout.end();
	}

	void start( void* window ) {
		// スレッドマネージャは一番最初にやる
		Threading::Manager::create( mExtraThreadNumber );
		if ( mArchiveNumber > 0 ) {
			Array< const char* > names( mArchiveNumber );
			for ( int i = 0; i < mArchiveNumber; ++i ) {
				names[ i ] = mArchiveNames[ i ].c_str();
			}
			FileIO::Manager::create( window, &names[ 0 ], mArchiveNumber, mLoadMode );
			//アーカイブ名配列は不要なので廃棄
			SAFE_DELETE_ARRAY( mArchiveNames );
			mArchiveNumber = 0;
		} else {
			FileIO::Manager::create( window );
		}
		Net::Manager::create();

		Graphics::Manager::create( window,
				0, 0,
				mEnableDepthStencil,
				mEnableAntialias,
				mEnableAnisotropicFilter,
				mTranslucent,
				true, false ); // Android では後ろの2つは無視される
		Input::Manager::create( window );
		Sound::Manager::create();
	}

	void preUpdate() {
		Core app;

		// 経過時間計測
		unsigned curTime = app.time();
#if ENABLE_TIME_CALC
		unsigned tmpPrev = mPreTime;

		// waitが必要か
		unsigned t1 = 1000 + mBresenhamError;
		unsigned t2 = mElapsedTime * mRequestFps;
		if ( t1 > t2 ) {
			// 待ち時間が必要な場合の処理
			unsigned waitTime = t1 - t2;
			mBresenhamError = waitTime % mRequestFps;
			waitTime -= mBresenhamError;
			unsigned idealFrameInterval = waitTime / mRequestFps;
			while ( ( curTime - mPreTime ) < idealFrameInterval ) {
				Threading::sleep( 0 ); // 他のやつに処理を回す
				curTime = app.time();
			}
		}
		// 経過を保持
		mPreTime = curTime;
		mElapsedTime = curTime - tmpPrev;
#else
		mElapsedTime = curTime - mPreTime;
		mPreTime = curTime;
#endif

		// FPS計測
		unsigned old;
		mTimeQ.popFront( &old );
		mFrameRate = static_cast< float >( mTimeQ.capacity() * 1000 ) / static_cast< float >( curTime - old );
		mTimeQ.pushBack( curTime );

		// 描画開始
		Graphics::Manager().beginRender();
		Input::Manager().update();
	}

	void postUpdate() {
		//文字描画
		gDebugScreenImpl->draw();
		// 描画終了
		Graphics::Manager().endRender();
	}

	unsigned mPreTime; // 前回の計測時間
	unsigned mElapsedTime; // 経過時間
#if ENABLE_TIME_CALC
	unsigned mRequestFps; // 希望FPS
	unsigned mBresenhamError; // ブレゼンハムのアルゴリズムの誤差分
#endif
	Queue< unsigned > mTimeQ; // 時間計測用のキュー
	float mFrameRate; // FPS値

	// Threading Config
	int mExtraThreadNumber;

	// FileIO Config
	FileIO::Manager::AccessMode mLoadMode;
	string* mArchiveNames;
	int mArchiveNumber;

	// Graphics Config
	bool mEnableDepthStencil;
	bool mEnableAntialias;
	bool mEnableAnisotropicFilter;
	bool mTranslucent;
};

static Impl* gImpl = 0;

} // namespace 無名

void Framework::Configuration::requestFps( unsigned fps ) {
	// no implement.
}

void Framework::Configuration::enableDepthStencil( bool f ) {
	gImpl->mEnableDepthStencil = f;
}

void Framework::Configuration::enableAntialias( bool f ) {
	gImpl->mEnableAntialias = f;
}

void Framework::Configuration::enableAnisotropicFilter( bool f ) {
	gImpl->mEnableAnisotropicFilter = f;
}

void Framework::Configuration::translucent( bool f ) {
	gImpl->mTranslucent = f;
}

void Framework::Configuration::setArchiveNumber( int n ){
	ASSERT( !gImpl->mArchiveNames && "you can't call twice." );
	gImpl->mArchiveNumber = n;
	gImpl->mArchiveNames = NEW string[ n ];
}

void Framework::Configuration::setArchiveName( int index, const char* name ){
	ASSERT( gImpl->mArchiveNames && "you must call setArchiveNumber() before this call." );
	ASSERT( index >= 0 && index < gImpl->mArchiveNumber );
	gImpl->mArchiveNames[ index ] = name;
}

void Framework::Configuration::setLoadMode( LoadMode lm ){
	using namespace FileIO;
	Manager::AccessMode am = Manager::MODE_DIRECT_FIRST;
	switch ( lm ){
		case LOAD_ARCHIVE_FIRST: am = Manager::MODE_ARCHIVE_FIRST; break;
		case LOAD_DIRECT_FIRST: am = Manager::MODE_DIRECT_FIRST; break;
		case LOAD_ARCHIVE_ONLY: am = Manager::MODE_ARCHIVE_ONLY; break;
		default: ASSERT( false ); break;
	}
	gImpl->mLoadMode = am;
}

void Framework::Configuration::loadFile( string* out, const char* filename, int dir ) const {
	ifstream in( filename, ifstream::binary );
	in.seekg( 0, ifstream::end );
	streamsize size = in.tellg();
	in.seekg( 0, ifstream::beg );
	char* s = new char[ size + 1 ];
	in.read( s, size );
	s[ size ] = '\0';
	*out = s;
	SAFE_DELETE_ARRAY( s );
}

Framework::Framework() {
	ASSERT( Core::isMainThread() && "you must call from MAIN thread" );
}

Framework Framework::instance() {
	return Framework();
}

float Framework::frameRate() const {
	return gImpl->mFrameRate;
}

unsigned Framework::elapsedTime() const {
	return gImpl->mElapsedTime;
}

// Core
void Core::configure( Configuration config ) {
	// コンフィグ時にしか生成機会はない
	gImpl = NEW Impl();
	// 最初にアプリ側に問い合わせ
	Framework().configure( Framework::Configuration() );

	// TODO:フレームワーク側で設定するものを設定
}

void Core::start( void* app ) {
	// 必要なものを初期化
	gImpl->start( app );
	// ユーザ定義初期化
	Framework().initialize();
}

void Core::update() {
	// GLSurfaceViewの実装を参考に...
	// http://code.google.com/p/apps-for-android/source/browse/trunk/SpriteMethodTest/src/com/android/spritemethodtest/GLSurfaceView.java
	// http://code.google.com/p/game4game/source/browse/
	Framework f;
	Graphics::Manager gm;
	bool onSleep = false;

	// ポーズ中？
	if ( isPause() ) {
		// ポーズ中ならグラフィックコンテキストを開放しておく
		if ( gm.canRender() ) {
			f.onPause();
			SAFE_DELETE( gDebugScreenImpl );
			gm.release();
		}
	}

	// ウィンドウがアクティブか
	if ( isActive() ) {
		// 描画可能かチェック
		if ( !gm.canRender() ) {
			// 上で開放されているので再生成します
			gm.restore();
			gDebugScreenImpl = NEW DebugScreenImpl();
			f.onResume();
			onSleep = true;
		}
		// 画面切り替え
		if ( isRequestedChangeDisplayMode() ) {
			gm.changeDisplayMode( false );
			requestChangeDisplayMode( false );
		}
	} else {
		onSleep = true;
	}

	// スリープチェック
	if ( onSleep ) {
		Threading::sleep( 50 );
	} else {
		gImpl->preUpdate();
		try {
			f.update();
		} catch ( ... ) {
			gImpl->postUpdate();
			finish();
			// アプリケーションレベルなのでEXITを上流へ通知
			throw EXCEPTION_EXIT;
		}
		gImpl->postUpdate();
	}
	// 終了処理
	if ( isRequestedEnd() ) {
		finish();
	}
}

void Core::stop() {
	Framework().terminate();
	SAFE_DELETE( gImpl );
}

} // namespace NTEngine
